package com.taldh.utils.beancopy;

import net.sf.cglib.beans.BeanCopier;
import net.sf.cglib.core.Converter;
import org.apache.commons.lang3.ArrayUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class ListBeanCopy {

    private final static Logger logger = LoggerFactory.getLogger(ListBeanCopy.class);

    static <T, K> K copy(T t, Class<K> k) {
        BeanCopier bc = BeanCopier.create(t.getClass(), k,
                false);
        K k1 = null;
        try {
            k1 = k.getConstructor().newInstance();
        } catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
            e.printStackTrace();
        }
        bc.copy(t,k1,null);

        Field[] sourceFields = t.getClass().getDeclaredFields();
        Field[] targetFields = k.getDeclaredFields();
        if (ArrayUtils.isEmpty(sourceFields) || ArrayUtils.isEmpty(targetFields)) {
            return null;
        }

        Map<String, Class> map = new HashMap<String, Class>();
        for (Field field : targetFields) {
            map.put(field.getName(), field.getType());
        }

        for (Field field : sourceFields) {
            String sourceFieldName = field.getName();
            if (!map.containsKey(sourceFieldName)) {
                continue;
            }

            Class sourceFieldType = field.getType();
            Class targetFieldType = map.get(sourceFieldName);

            if (!sourceFieldType.isEnum() && !targetFieldType.isEnum()) {
                continue;
            }

            try {
                String methodName = sourceFieldName.substring(0, 1).toUpperCase() + sourceFieldName.substring(1);
                Method getMethod = null;
                try {
                    getMethod = t.getClass().getMethod("get" + methodName);
                } catch (Exception e) {
                    logger.warn("method get{} not existed!", methodName);
                    continue;
                }
                Object value = getMethod.invoke(t);

                Method setMethod = null;
                try {
                    setMethod = k.getMethod("set" + methodName, targetFieldType);
                } catch (Exception e) {
                    logger.warn("method set{} not existed!", methodName);
                    continue;
                }

                if (sourceFieldType.isEnum() && Integer.class.equals(targetFieldType)) { // Enum-->Integer
                    if (value == null) {
                        setMethod.invoke(k1, value);
                    } else {
                        Object[] enumItems = sourceFieldType.getEnumConstants();
                        for (Object enumItem : enumItems) {
                            if (enumItem.equals(value)) {
                                Method getCodeMethod = sourceFieldType.getMethod("getCode");
                                value = getCodeMethod.invoke(enumItem);
                                setMethod.invoke(k1, value);
                                break;
                            }
                        }
                    }
                } else if (Integer.class.equals(sourceFieldType) && targetFieldType.isEnum()) { // Integer-->Enum
                    Method findMethod = targetFieldType.getMethod("find", Integer.class);
                    value = findMethod.invoke(targetFieldType, value == null ? null : value);
                    setMethod.invoke(k1, value);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }


        return k1;
    }

    public static <T, K> List<K> copy(List<T> ts, Class<K> k) {
        List<K> list = new ArrayList<K>();
        for (T t : ts) {
            list.add(copy2(t, k));
        }
        return list;
    }

    public static <T, K> K copy2(T t, Class<K> k) {
        BeanCopier bc = BeanCopier.create(t.getClass(), k,
                true);
        K k1 = null;
        try {
            k1 = k.getConstructor().newInstance();
        } catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
            e.printStackTrace();
        }
        bc.copy(t, k1, new Converter() {
            @Override
            public Object convert(Object sourceValue, Class targetClass, Object methodName) {
                if (sourceValue == null) {
                    return null;
                }

                if (!sourceValue.getClass().isEnum() && !targetClass.isEnum()) {
                    return sourceValue;
                }

                // Enum-->Integer
                if (sourceValue.getClass().isEnum() && Integer.class.isAssignableFrom(targetClass)) {
                    try {
                        return sourceValue.getClass().getMethod("getCode").invoke(sourceValue);
                    } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
                        logger.error("method getCode not existed in enum {}!", sourceValue);
                    }
                }

                // Integer-->Enum
                if (targetClass.isEnum() && Integer.class.isAssignableFrom(sourceValue.getClass())) {
                    try {
                        return targetClass.getMethod("find", Integer.class).invoke(targetClass, sourceValue);
                    } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
                        logger.error("method getCode not existed in enum {}!", sourceValue);
                    }
                }

                return sourceValue;

            }
        });

        return k1;
    }

}
